/* Copyright 2012 Tobias Marschall
 *
 * This file is part of CLEVER.
 *
 * CLEVER is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * CLEVER is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with CLEVER.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef GROUPWISEBAMREADER_H_
#define GROUPWISEBAMREADER_H_

#include <iostream>
#include <vector>
#include <boost/unordered_set.hpp>

#include <bamtools/api/BamReader.h>

/** Reads a file in BAM format and returns groups of alignments belonging to
 *  the same read. Expects alignments to be grouped by read name in input BAM
 *  and throws an exception otherwise. */
class GroupWiseBamReader {
private:
	BamTools::BamReader bam_reader;
	std::string read_name;
	// alignments of the first and second read in the current read pair
	std::vector<BamTools::BamAlignment> alignments1;
	std::vector<BamTools::BamAlignment> alignments2;
	// read names to verify that reads are properly grouped
	boost::unordered_set<std::string> read_names;
	// count all processed alignments
	long long counter;
	std::ostream* progress_messages_os;
	int progress_message_frequency;
	BamTools::BamAlignment next_read_aln;
	bool finished;
	long long skipped_duplicates;
	bool paired;
	bool unmapped1;
	bool unmapped2;
	bool inconsistentXTags;
	std::string inconsistentXTagsMessage;
	bool multiplyMappedButNoXA;
	long long hashReadStart(const BamTools::BamAlignment& aln);
	void addRead(std::vector<BamTools::BamAlignment>& alignments, boost::unordered_set<long long>& alignments_starts, const BamTools::BamAlignment& read_aln);
	/** Verifies whether the number of reads complies with the given X0/X1 tags (if given). */
	void checkX0X1Tags(const std::vector<BamTools::BamAlignment>& alignments, int count);

public:
	/** If paired is true, then the input BAM is expected to be paired end data. In this case, the methods
	 *  getAlignmentsFirst and getAlignmentsSecond must be used (instead of getAlignments) to get the
	 *  alignments of first/second read end. */
	GroupWiseBamReader(const std::string& filename, bool paired);
	virtual ~GroupWiseBamReader();

	/** Returns true if there is another read pair left in the input file. */
	bool hasNext() const;

	/** Reads next read pair. Before calling this method, hasNext() should
	 *  be called. */
	void advance();

	/** Returns the name of the currently processed read. */
	const std::string& getReadName() const { return read_name; }

	/** Returns a reference to alignments current read. May only be called when
	 *  paired = false was given at construction time.
	 *  The returned reference is valid until the next call to advance(). */
	const std::vector<BamTools::BamAlignment>& getAlignments() const;

	/** Returns a reference to alignments of first read in current read pair.
	 *  The returned reference is valid until the next call to advance(). */
	const std::vector<BamTools::BamAlignment>& getAlignmentsFirst() const;

	/** Returns a reference to alignments of second read in current read pair.
	 *  The returned reference is valid until the next call to advance(). */
	const std::vector<BamTools::BamAlignment>& getAlignmentsSecond() const;

	bool isUnmapped() const;
	bool isFirstUnmapped() const;
	bool isSecondUnmapped() const;

	bool hasInconsistentXTags() const;
	bool hasMultipleMappingsButNoXA() const;
	const std::string& getInconsistentXTagsMessage() const;

	void enableProgressMessages(std::ostream& os, int frequency);

	long long getSkippedDuplicates() const;

	const BamTools::RefVector& getReferenceData() const { return bam_reader.GetReferenceData(); }

	BamTools::SamHeader getHeader() const { return bam_reader.GetHeader(); }
};

#endif /* GROUPWISEBAMREADER_H_ */
